<?php
    // +----------------------------------------------------------------------
    // | Created by PHPstorm: JRKAdmin框架 [ JRKAdmin ] 
    // +----------------------------------------------------------------------
    // | Copyright (c) 2019~2022 [LuckyHHY] All rights reserved.
    // +----------------------------------------------------------------------
    // | SiteUrl: http://www.luckyhhy.cn
    // +----------------------------------------------------------------------
    // | Author: LuckyHhy <jackhhy520@qq.com>
    // +----------------------------------------------------------------------
    // | Date: 2020/3/26-10:52
    // +----------------------------------------------------------------------
    // | Description:  
    // +----------------------------------------------------------------------


    namespace app\admin\controller\queue;

    use app\common\controller\BaseController;
    use Cron\CronExpression;
    use Jrk\Http;
    use liliuwei\think\Jump;
    use think\facade\Db;

    /**
     * 定时任务接口
     *
     * 以Crontab方式每分钟定时执行,且只可以Cli方式运行
     * @internal
     */

    class Autotask extends BaseController
    {
        use Jump;
        /**
         * 初始化方法,最前且始终执行
         */
        public function _initialize()
        {
            // 只可以以cli方式执行
            if (!$this->request->isCli()) {

                $this->error('Autotask script only work at client!');

            }

            parent::_initialize();

            // 清除错误
            error_reporting(0);

            // 设置永不超时
            set_time_limit(0);
        }

        /**
         * @return string
         * @throws \think\db\exception\DataNotFoundException
         * @throws \think\db\exception\DbException
         * @throws \think\db\exception\ModelNotFoundException
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @date: 2020/7/30 0030
         * @describe:执行定时任务
         */
        public function index()
        {
            $time = time();
            $logDir = app()->getRuntimePath() ."log/".'crontab/';

            if (!is_dir($logDir)) {
                mkdir($logDir, 0755);
            }
            //筛选未过期且未完成的任务
            $crontabList = \app\admin\model\Queues::where('status', '=', 'normal')->order('weigh desc,id desc')->select();
            $execTime = time();
            foreach ($crontabList as $crontab) {
                $update = [];
                $execute = false;
                if ($time < $crontab['begintime']) {
                    //任务未开始
                    continue;
                }
                if ($crontab['maximums'] && $crontab['executes'] > $crontab['maximums']) {
                    //任务已超过最大执行次数
                    $update['status'] = 'completed';
                } else {
                    if ($crontab['endtime'] > 0 && $time > $crontab['endtime']) {
                        //任务已过期
                        $update['status'] = 'expired';
                    } else {
                        //重复执行
                        //如果未到执行时间则继续循环
                        $cron = CronExpression::factory($crontab['schedule']);
                        if (!$cron->isDue(date("YmdHi", $execTime)) || date("YmdHi", $execTime) === date("YmdHi", $crontab['executetime'])) {
                            continue;
                        }
                        $execute = true;
                    }
                }

                // 如果允许执行
                if ($execute) {
                    $update['executetime'] = $time;
                    $update['executes'] = $crontab['executes'] + 1;
                    $update['status'] = ($crontab['maximums'] > 0 && $update['executes'] >= $crontab['maximums']) ? 'completed' : 'normal';
                }

                // 如果需要更新状态
                if (!$update) {
                    continue;
                }
                // 更新状态
                $crontab->save($update);

                // 将执行放在后面是为了避免超时导致多次执行
                if (!$execute) {
                    continue;
                }
                $result = false;
                $message = '';

                try {
                    if ($crontab['type'] == 'url') {
                        if (substr($crontab['content'], 0, 1) == "/") {
                            // 本地项目URL
                            $message = shell_exec('php ' . app()->getRootPath() . 'public/index.php ' . $crontab['content']);
                            $result = $message ? true : false;
                        } else {
                            $arr = explode(" ", $crontab['content']);
                            $url = $arr[0];
                            $params = isset($arr[1]) ? $arr[1] : '';
                            $method = isset($arr[2]) ? $arr[2] : 'POST';
                            try {
                                // 远程异步调用URL
                                $ret = Http::sendRequest($url, $params, $method);
                                $result = $ret['ret'];
                                $message = $ret['msg'];
                            } catch (\Exception $e) {
                                $message = $e->getMessage();
                            }
                        }

                    } elseif ($crontab['type'] == 'sql') {
                        $ret = $this->sql($crontab['content']);
                        $result = $ret['ret'];
                        $message = $ret['msg'];
                    } elseif ($crontab['type'] == 'shell') {
                        // 执行Shell
                        $message = shell_exec($crontab['content']);
                        $result = $message ? true : false;
                    }
                } catch (\Exception $e) {
                    $message = $e->getMessage();
                }
                $log = [
                    'crontab_id'   => $crontab['id'],
                    'executetime'  => $time,
                    'completetime' => time(),
                    'content'      => $message,
                    'status'       => $result ? 'success' : 'failure',
                ];
                Db::name("crontab_log")->insert($log);
            }
            return "Execute completed!\n";
        }



        /**
         * @param $sql
         * @return array
         * @throws \think\Exception
         * @author: LuckyHhy <jackhhy520@qq.com>
         * @date: 2020/3/26
         * @name: sql
         * @describe:执行SQL语句
         */
        protected function sql($sql)
        {
            //这里需要强制重连数据库,使用已有的连接会报2014错误
            $connect = Db::connect([], true);
            $connect->execute("select 1");

            // 执行SQL
            $sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sql);
            $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);

            $result = false;
            $message = '';
            $connect->startTrans();
            try {
                foreach ($sqls as $key => $val) {
                    if (trim($val) == '' || substr($val, 0, 2) == '--' || substr($val, 0, 2) == '/*') {
                        continue;
                    }
                    $message .= "\nSQL:{$val}\n";
                    $val = rtrim($val, ';');
                    if (preg_match("/^(select|explain)(.*)/i ", $val)) {
                        $count = $connect->execute($val);
                        if ($count > 0) {
                            $resultlist = Db::query($val);
                        } else {
                            $resultlist = [];
                        }

                        $message .= "Total:{$count}\n";
                        $j = 1;
                        foreach ($resultlist as $m => $n) {
                            $message .= "\n";
                            $message .= "Row:{$j}\n";
                            foreach ($n as $k => $v) {
                                $message .= "{$k}：{$v}\n";
                            }
                            $j++;
                        }
                    } else {
                        $count = $connect->getPdo()->exec($val);
                        $message = "Affected rows:{$count}";
                    }
                }
                $connect->commit();
                $result = true;
            } catch (\PDOException $e) {
                $message = $e->getMessage();
                $connect->rollback();
                $result = false;
            }
            return ['ret' => $result, 'msg' => $message];

        }

    }